package compiler

import (
	"kumachan/lang/source"
	"kumachan/lang/typsys"
	"kumachan/lang/textual/ast"
	"kumachan/interpreter/program"
)


func checkCast(cast ast.Cast, in ast.Expr, cc *exprCheckContext) (*program.Expr, *source.Error) {
	var loc = cast.Location
	var target, err = cc.context.makeType(cast.Target)
	if err != nil { return nil, err }
	{ var expr, err = cc.checkChildExpr(target.Type, in)
	if err != nil { return nil, err }
	return cc.assign(target, loc,
		program.Wrapper { Inner: expr })
	}
}

func checkPipeGet(get ast.PipeGet, in ast.Expr, cc *exprCheckContext) (*program.Expr, *source.Error) {
	var key = ast.Id2String(get.Key)
	var loc = get.Location
	var ctx = cc.context
	if ref_node, is_new, is_ref := getStandaloneRefFromExpr(in); is_ref {
		if !(is_new) {
		if def, ok := ctx.resolveType(getRef(ref_node.Base)); ok {
		if enum, ok := def.Content.(typsys.Enum); ok {
			var type_node = ast.Type {
				Node: ref_node.Node,
				Ref:  ref_node,
			}
			var type_, err = ctx.makeType(type_node)
			if err != nil { return nil, err }
			var index, exists = enum.FieldIndexMap[key]
			if !(exists) {
				return cc.error(loc,
					E_NoSuchEnumItem {
						Item: key,
						Enum: def.Ref.String(),
					})
			}
			return cc.assign(type_, loc,
				program.Enum(index),
			)
		}
	}}}
	var in_expr, err = cc.checkChildExpr(nil, in)
	if err != nil { return nil, err }
	var in_t = in_expr.Type
	if R, Rd, Ra, ok := getRecord(in_t.Type, ctx); ok {
		var index, exists = R.FieldIndexMap[key]
		if exists {
			var field = R.FieldList[index]
			var field_t_ = inflateFieldType(field, Rd, Ra)
			var field_t = typsys.CertainType { Type: field_t_ }
			return cc.assign(field_t, loc,
				program.FieldValue {
					Record: in_expr,
					Index:  index,
				})
		}
	}
	if method_t, f, index, path, ok := ctx.resolveMethod(in_t, key, nil); ok {
		if f != nil {
			return cc.assign(method_t, loc,
				program.ConcreteMethodValue {
					This:   in_expr,
					Path:   path,
					Method: f,
				})
		} else {
			return cc.assign(method_t, loc,
				program.AbstractMethodValue {
					Interface: in_expr,
					Path:      path,
					Index:     index,
				})
		}
	}
	return cc.error(loc,
		E_NoSuchFieldOrMethod {
			FieldName: key,
			TypeDesc:  typsys.DescribeCertain(in_t),
		})
}

func checkPipeInterior(interior ast.PipeInterior, in ast.Expr, cc *exprCheckContext) (*program.Expr, *source.Error) {
	var ref = getRef(interior.RefBase)
	var loc = interior.Location
	var ctx = cc.context
	var in_expr, err = cc.checkChildExpr(nil, in)
	if err != nil { return nil, err }
	var in_t = in_expr.Type
	if ref.Namespace == "" {
		var key = ref.ItemName
		if r, d, a, o, b, ok := getInteriorReferableRecord(in_t, ctx); ok {
			var index, exists = r.FieldIndexMap[key]
			if !(exists) { goto NG }
			var field = r.FieldList[index]
			var field_t_ = inflateFieldType(field, d, a)
			var lens1_t_ = program.T_Lens1(b.Type, field_t_)
			var lens1_t = typsys.CertainType { Type: lens1_t_}
			return cc.assign(lens1_t, loc,
				program.InteriorRef {
					Base:    in_expr,
					Index:   index,
					Kind:    program.RK_RecordField,
					Operand: o,
				})
		}
		if u, d, a, o, b, ok := getInteriorReferableUnion(in_t, ctx); ok {
			var index, exists = u.FieldIndexMap[key]
			if !(exists) { goto NG }
			var field = u.FieldList[index]
			var field_t_ = inflateFieldType(field, d, a)
			var lens2_t_ = program.T_Lens2(b.Type, field_t_)
			var lens2_t = typsys.CertainType { Type: lens2_t_ }
			return cc.assign(lens2_t, loc,
				program.InteriorRef {
					Base:    in_expr,
					Index:   index,
					Kind:    program.RK_UnionItem,
					Operand: o,
				})
		}
	}
	if _, Id, Ia, o, b, ok := getInteriorReferableInterface(in_t, ctx); ok {
		var d, exists = ctx.resolveType(ref)
		if !(exists) { goto NG }
		var _, is_interface = d.Content.(typsys.Interface)
		if is_interface { goto NG }
		var table, ok = ctx.resolveDispatchTable(d, Id)
		if !(ok) { goto NG }
		var d_type = typsys.RefType { Def: d.Ref, Args: Ia }
		var lens2_t_ = program.T_Lens2(b.Type, d_type)
		var lens2_t = typsys.CertainType { Type: lens2_t_ }
		return cc.assign(lens2_t, loc,
			program.InteriorRef {
				Base:    in_expr,
				Table:   table,
				Kind:    program.RK_DynamicCast,
				Operand: o,
			})
	}
	NG:
	return cc.error(loc,
		E_InteriorRefUnavailable {
			InteriorRef: ref.String(),
			TypeDesc:    typsys.DescribeCertain(in_t),
		})
}

func checkPipeInfix(infix ast.PipeInfix, in ast.Expr, cc *exprCheckContext) (*program.Expr, *source.Error) {
	var left, err = cc.checkChildExpr(nil, in)
	if err != nil { return nil, err }
	var right = infix.PipeCall
	var op = infix.Callee
	var loc = infix.Location
	return checkCallFunRef(op, left, right, true, loc, cc)
}
func checkInfixTerm(I ast.InfixTerm, cc *exprCheckContext) (*program.Expr, *source.Error) {
	var left, err = cc.checkChildExpr(nil, I.Left)
	if err != nil { return nil, err }
	var right = craftInfixRight(I.Right, I.Node)
	var op = I.Operator
	var loc = I.Location
	return checkCallFunRef(op, left, right, true, loc, cc)
}

func checkPipeCall(args ast.VariousPipeCall, callee ast.Expr, cc *exprCheckContext) (*program.Expr, *source.Error) {
	var loc = args.Location
	if ref_node, is_new, is_ref := getStandaloneRefFromExpr(callee); is_ref {
		if is_new {
			return checkNewRecord(ref_node, args, loc, cc)
		} else {
			var ctx = cc.context
			var ref = getRef(ref_node.Base)
			if ((ref.Namespace == "") && ctx.hasBinding(ref.ItemName)) {
				return checkCallExpr(callee, args, cc)
			} else {
				return checkCallFunRef(ref_node, nil, args, false, loc, cc)
			}
		}
	} else {
		return checkCallExpr(callee, args, cc)
	}
}


func getStandaloneTermFromExpr(expr ast.Expr) (ast.VariousTerm, bool) {
	if (len(expr.Casts) == 0) && (len(expr.Pipeline) == 0) {
		return expr.Term, true
	} else {
		return ast.VariousTerm {}, false
	}
}
func getStandaloneRefFromExpr(expr ast.Expr) (ast.Ref, bool, bool) {
	if term, ok := getStandaloneTermFromExpr(expr); ok {
		var ref_term, ok = term.Term.(ast.RefTerm)
		return ref_term.Ref, ref_term.New, ok
	} else {
		return ast.Ref {}, false, false
	}
}
func getInteriorReferableRecord(base_t typsys.CertainType, ctx *exprContext) (typsys.Record, *typsys.TypeDef, ([] typsys.Type), program.InteriorRefOperand, typsys.CertainType, bool) {
	if record, def, args, ok := getRecord(base_t.Type, ctx); ok {
		return record, def, args, program.RO_Direct, base_t, true
	}
	if base_t_, field_t, ok := program.T_Lens1_(base_t.Type); ok {
		var base_t = typsys.CertainType { Type: base_t_ }
		var record, def, args, ok = getRecord(field_t, ctx)
		if ok {
			return record, def, args, program.RO_Lens1, base_t, true
		}
	}
	return typsys.Record{}, nil, nil, -1, typsys.CertainType{}, false
}
func getInteriorReferableUnion(base_t typsys.CertainType, ctx *exprContext) (typsys.Union, *typsys.TypeDef, ([] typsys.Type), program.InteriorRefOperand, typsys.CertainType, bool) {
	if union, def, args, ok := getUnion(base_t.Type, ctx); ok {
		return union, def, args, program.RO_Direct, base_t, true
	}
	if base_t_, field_t, ok := program.T_Lens1_(base_t.Type); ok {
		var base_t = typsys.CertainType { Type: base_t_ }
		var union, def, args, ok = getUnion(field_t, ctx)
		if ok {
			return union, def, args, program.RO_Lens1, base_t, true
		}
	}
	if base_t_, field_t, ok := program.T_Lens2_(base_t.Type); ok {
		var base_t = typsys.CertainType { Type: base_t_ }
		var union, def, args, ok = getUnion(field_t, ctx)
		if ok {
			return union, def, args, program.RO_Lens2, base_t, true
		}
	}
	return typsys.Union{}, nil, nil, -1, typsys.CertainType{}, false
}
func getInteriorReferableInterface(base_t typsys.CertainType, ctx *exprContext) (typsys.Interface, *typsys.TypeDef, ([] typsys.Type), program.InteriorRefOperand, typsys.CertainType, bool) {
	if interface_, def, args, ok := getInterface(base_t.Type, ctx); ok {
		return interface_, def, args, program.RO_Direct, base_t, true
	}
	if base_t_, field_t, ok := program.T_Lens1_(base_t.Type); ok {
		var base_t = typsys.CertainType { Type: base_t_ }
		var interface_, def, args, ok = getInterface(field_t, ctx)
		if ok {
			return interface_, def, args, program.RO_Lens1, base_t, true
		}
	}
	if base_t_, field_t, ok := program.T_Lens2_(base_t.Type); ok {
		var base_t = typsys.CertainType { Type: base_t_ }
		var interface_, def, args, ok = getInterface(field_t, ctx)
		if ok {
			return interface_, def, args, program.RO_Lens2, base_t, true
		}
	}
	return typsys.Interface{}, nil, nil, -1, typsys.CertainType{}, false
}


